package iceberg.pmd;

import iceberg.Reference;
import iceberg.rules.RuleDispatcher;
import iceberg.constraints.ConstraintException;
import iceberg.constraints.IConstraintResult;
import iceberg.domain.Unit;
import net.sourceforge.pmd.AbstractJavaRule;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.ast.ASTClassOrInterfaceType;
import net.sourceforge.pmd.ast.ASTImportDeclaration;
import net.sourceforge.pmd.ast.SimpleNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

public class PMDArchitectureRule extends AbstractJavaRule {

	private static final Logger LOG = LoggerFactory.getLogger(PMDArchitectureRule.class);
	private final RuleDispatcher dispatcher = new RuleDispatcher();

//	@Override
//	public Object visit(ASTExtendsList node, Object data) {
//		node.getFirstChildOfType(ASTClassOrInterfaceType.class).getType();
//
//		return super.visit(node, data);
//	}

//	@Override
//	public Object visit(ASTReferenceType node, Object data) {
//		Class<?> type = node.getType();
//		if (type != null) {
//			LOG.info("Found ASTReferenceType '{}'", type.getName());
//			check(Unit.createClassUnit(node.getType().getName()), node, data);
//		}
//		return super.visit(node, data);
//	}
//
//	@Override
//	public Object visit(ASTLocalVariableDeclaration node, Object data) {
//		try {
//			Class variable = node.getTypeNode().getType();
//
//			if (variable != null && !variable.isPrimitive()) {
//				Unit unit = Unit.createClassUnit(variable.getName());
//				LOG.info("Local variable '{}'", variable.getName());
//				check(unit, node, data);
//			}
//		} catch (Exception ex) {
//			LOG.error("localvariable", ex);
//		}
//		return super.visit(node, data);
//	}

	@Override
	public Object visit(ASTClassOrInterfaceType node, Object data) {

		Class<?> type = node.getType();
		if (type != null) {
			LOG.info("Found ASTClassOrInterfaceType '{}'", type.getName());
			Unit unit = Unit.createClassUnit(type.getName());
			check(unit, node, data);
		}
		return super.visit(node, data);
	}

//	@Override
//	public Object visit(ASTType node, Object data) {
//		Class<?> type = node.getType();
//		if (type != null && !type.isPrimitive()) {
//			LOG.info("Found type '{}'", type.getName());
//			check(new Unit(Unit.Type.CLASS, type.getName()), node, data);
//		}
//
//		return super.visit(node, data);
//	}

//	@Override
//	public Object visit(ASTVariableInitializer node, Object data) {
//		try {
//			ASTClassOrInterfaceType type = node.getFirstChildOfType(ASTClassOrInterfaceType.class);
//			if (type != null) {
//				Class<?> innerType = type.getType();
//				if (innerType != null) {
//					LOG.info("Found variable initializer '{}'", innerType.getName());
//					check(Unit.createClassUnit(innerType.getName()), node, data);
//				}
//			}
//		} catch (Exception e) {
//			LOG.error("An exception occurred", e);
//		}
//
//		return super.visit(node, data);
//	}

	@Override
	public Object visit(ASTImportDeclaration node, Object data) {
		if (!node.isImportOnDemand()) {
			Unit location = Unit.createClassUnit(node.getImportedName());
			LOG.info("Found ASTImportDeclaration '{}'", location.getName());
			check(location, node, data);
		}
		return super.visit(node, data);
	}

	private void check(Unit to, SimpleNode node, Object data) {
		try {
			RuleContext context = (RuleContext) data;
			Unit from = Unit.createUnitFromRawFile(context.getSourceCodeFilename());
			Reference reference = new Reference(from, to);

			List<IConstraintResult> results = dispatcher.invokeRules(reference);
			for (IConstraintResult result : results) {
				if (!result.isAllowed()) {
					ConstraintException exception = dispatcher.getConstraintException(reference);
					if (exception != null) {
						LOG.info("Exception found for " + reference.getFrom().getName() + " reason: " + exception.getArgument());
					} else {
						addViolationWithMessage(data, node, "Rule '" + result.getName() + "' is violated by " + result.getType().getDescription());
					}
				}
			}
		} catch (Exception e) {
			LOG.error("An exception occurred", e);
		}
	}
}
